﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Epsilon
{
    public abstract class Type 
    {
        public override bool Equals(object obj)
        {
            return (obj is Type) && this == ((Type)obj);
        }

        public static bool operator ==(Type left, Type right)
        {
            if (System.Object.ReferenceEquals(left, right))
                return true;

            if ((object)left == null || (object)right == null)
                return false;

            if ((left is SimpleType) && (right is SimpleType))
                return (SimpleType)left == (SimpleType)right;

            if ((left is AtomType) && (right is AtomType))
                return (AtomType)left == (AtomType)right;

            if ((left is TupleType) && (right is TupleType))
                return (TupleType)left == (TupleType)right;

            if ((left is ListType) && (right is ListType))
                return (ListType)left == (ListType)right;

            if ((left is FunType) && (right is FunType))
                return (FunType)left == (FunType)right;

            return false;
        }

        public static bool operator !=(Type left, Type right)
        {
            return !(left == right);
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
    }

    public enum BaseType
    {
        Int,
        Bool,
        String,
        Unit,
        Pid
    }

    public class SimpleType : Type
    {
        public static readonly SimpleType IntType = new SimpleType(BaseType.Int);
        public static readonly SimpleType BoolType = new SimpleType(BaseType.Bool);
        public static readonly SimpleType StringType = new SimpleType(BaseType.String);
        public static readonly SimpleType UnitType = new SimpleType(BaseType.Unit);
        public static readonly SimpleType PidType = new SimpleType(BaseType.Pid);
        public BaseType Type { get; private set; }

        public SimpleType(BaseType t)
        {
            Type = t;
        }

        public override string ToString()
        {
            switch (Type)
            {
                case BaseType.Int:
                    return "int";
                case BaseType.Bool:
                    return "bool";
                case BaseType.String:
                    return "string";
                case BaseType.Unit:
                    return "unit";
                case BaseType.Pid:
                    return "pid";
                default:
                    return "";
            }
        }

        public override bool Equals(object obj)
        {
            return (obj is Type) && this == (Type)obj;
        }

        public static bool operator ==(SimpleType left, SimpleType right)
        {
            if (System.Object.ReferenceEquals(left, right))
                return true;

            if ((object)left == null || (object)right == null)
                return false;

            return left.Type == right.Type;
        }

        public static bool operator !=(SimpleType left, SimpleType right)
        {
            return !(left == right);
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
    }

    public class AtomType : Type
    {
        public string Value { get; private set; }

        public AtomType(string s)
        {
            Value = s;
        }

        public override string ToString()
        {
            if (Array.IndexOf(new string[] { "int", "bool", "string", "unit", "pid" }, Value) >= 0)
                return "'" + Value + "'";
            else
                return Value;
        }

        public override bool Equals(object obj)
        {
            return (obj is Type) && this == (Type)obj;
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(AtomType left, AtomType right)
        {
            if (System.Object.ReferenceEquals(left, right))
                return true;

            if ((object)left == null || (object)right == null)
                return false;

            return left.Value == right.Value;
        }

        public static bool operator !=(AtomType left, AtomType right)
        {
            return right != left;
        }
    }

    public class ListType : Type
    {
        public Type ElementType { get; private set; }

        public ListType(Type t)
        {
            ElementType = t;
        }

        public override string ToString()
        {
            return "[" + ElementType.ToString() + "]";
        }

        public override bool Equals(object obj)
        {
            return (obj is Type) && this == (Type)obj;
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(ListType left, ListType right)
        {
            return left.ElementType == right.ElementType;
        }

        public static bool operator !=(ListType left, ListType right)
        {
            return right != left;
        }
    }

    public class TupleType : Type
    {
        public List<Type> Types { get; private set; }

        public TupleType(List<Type> ts)
        {
            Types = ts;
        }

        public override string ToString()
        {
            string s = "{";

            for (int i = 0; i < Types.Count; i++)
            {
                if (i > 0)
                    s += ", ";
                s += Types[i].ToString();
            }

            return s + "}";
        }

        public override bool Equals(object obj)
        {
            return (obj is Type) && this == (Type)obj;
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(TupleType left, TupleType right)
        {
            if (System.Object.ReferenceEquals(left, right))
                return true;

            if ((object)left == null || (object)right == null)
                return false;

            if (left.Types.Count != right.Types.Count)
                return false;

            for (int i = 0; i < left.Types.Count; i++)
            {
                if (left.Types[i] != right.Types[i])
                    return false;
            }

            return true;
        }

        public static bool operator !=(TupleType left, TupleType right)
        {
            return right != left;
        }
    }

    public class FunType : Type
    {
        public List<Type> Args { get; private set; }
        public Type Ret { get; private set; }

        public FunType(List<Type> a, Type r)
        {
            Args = a;
            Ret = r;
        }

        public override string ToString()
        {
            string s = "fun(";

            for (int i = 0; i < Args.Count; i++)
            {
                if (i > 0)
                    s += ", ";
                s += Args[i].ToString();
            }

            return s + ") -> " + Ret.ToString();
        }

        public override bool Equals(object obj)
        {
            return (obj is Type) && this == (Type)obj;
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public static bool operator ==(FunType left, FunType right)
        {
            if (System.Object.ReferenceEquals(left, right))
                return true;

            if ((object)left == null || (object)right == null)
                return false;

            if (left.Args.Count != right.Args.Count)
                return false;

            for (int i = 0; i < left.Args.Count; i++)
            {
                if (left.Args[i] != right.Args[i])
                    return false;
            }

            return left.Ret == right.Ret;
        }

        public static bool operator !=(FunType left, FunType right)
        {
            return right != left;
        }
    }
}
